package com.avengers.core.kit;

import com.avengers.core.bean.Numbers;
import com.avengers.core.bean.enums.SymbolEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.util.Date;

import static com.avengers.core.bean.Numbers.*;
import static com.avengers.core.kit.RegexKit.*;

/**
 * 美国有几个时区？中美时区如何计算？美国分4个时区，从西岸到东岸的方向分为：
 * 　　西部标准时间区PST(太平洋标准时间区Pacific Standard Time)：较中国时间慢16小时。
 * 　　山岳标准时间区MST(Mountain Standard Time)：较中国时间慢15小时
 * 　　中部标准时间区CST(Center Standard Time)：较中国时间慢14小时
 * 　　东部标准时间区EST(Eastern Standard Time)：较中国时间慢13小时
 * 　　(每年4~10月为加夏令时期，时钟需往前拨快一小时，分别差15，14，13，12小时。)
 *
 * @author yu.wang
 * @since 2019/2/1 上午10:59
 **/
public class DateKit {
    public static final int DATE_TIME_DEFAULT_LENGTH = 19;
    public static final int DATE_DEFAULT_LENGTH = 10;
    public static final String END_TIME_00 = "00:00:00";
    private static final String DEFAULT_ZERO_ZONE_REGEX = "YYYYMMdd'T'HHmmss'Z'";
    private static final Logger logger = LoggerFactory.getLogger(DateKit.class);

    public static void setDatePattern(String datePattern) {
        com.jfinal.ext.kit.DateKit.setDatePattern(datePattern);
    }

    public static void setTimeStampPattern(String timeStampPattern) {
        com.jfinal.ext.kit.DateKit.setTimeStampPattern(timeStampPattern);
    }

    static final String YMD_SYMBOL = "[\\./-]";
    static final String HMS_SYMBOL = SymbolEnum.colon.string;

    private static Date logInvalidDate(String dateStr) {
        logger.error("{} can not format to Date", dateStr);
        return null;
    }

    private static Integer parseInt(String number) {
        if (null == number || number.isEmpty() || RegexKit.notMatch(NUMBER, number)) {
            return null;
        }
        return Integer.parseInt(number.trim());
    }

    public static Date of(String dateStr) {
        if (null == dateStr
                || dateStr.isEmpty()
                || dateStr.length() == INT_3
                || dateStr.length() == INT_9
                || dateStr.length() == INT_11
                || dateStr.length() == INT_12) {
            return logInvalidDate(dateStr);
        }
        LocalDateTime localDateTime = null;
        if (dateStr.length() <= INT_2) {
            localDateTime = LocalDate.of(LocalDate.now().getYear(), LocalDate.now().getMonthValue(), parseInt(dateStr)).atStartOfDay();
        } else if (dateStr.length() == INT_4 || dateStr.length() == INT_5) {
            if (RegexKit.contains(dateStr, YMD_SYMBOL)) {
                String[] arrays = dateStr.split(YMD_SYMBOL);
                localDateTime = LocalDate.of(LocalDate.now().getYear(), parseInt(arrays[INT_0]), parseInt(arrays[INT_1])).atStartOfDay();
            } else if (RegexKit.contains(dateStr, HMS_SYMBOL)) {
                String[] arrays = dateStr.split(HMS_SYMBOL);
                localDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.of(parseInt(arrays[INT_0]), parseInt(arrays[INT_1])));
            } else if (dateStr.length() == INT_4) {
                localDateTime = LocalDate.of(LocalDate.now().getYear(), parseInt(dateStr.substring(INT_0, INT_2)), parseInt(dateStr.substring(INT_2, INT_4))).atStartOfDay();
            } else {
                return logInvalidDate(dateStr);
            }
        } else if (dateStr.length() == INT_6 || dateStr.length() == INT_7) {
            if (RegexKit.contains(dateStr, YMD_SYMBOL)) {
                String[] arrays = dateStr.split(YMD_SYMBOL);
                if (arrays.length != INT_3) {
                    return logInvalidDate(dateStr);
                }
                localDateTime = LocalDate.of(parseInt(arrays[INT_0]), parseInt(arrays[INT_1]), parseInt(arrays[INT_2])).atStartOfDay();
            } else if (RegexKit.contains(dateStr, HMS_SYMBOL)) {
                String[] arrays = dateStr.split(HMS_SYMBOL);
                if (arrays.length != INT_3) {
                    return logInvalidDate(dateStr);
                }
                localDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.of(parseInt(arrays[INT_0]), parseInt(arrays[INT_1]), parseInt(arrays[INT_2])));
            } else if (dateStr.length() == INT_6 && RegexKit.match(NUMBER, dateStr)) {
                String year = String.valueOf(LocalDate.now().getYear()).substring(INT_0, INT_2) + dateStr.substring(INT_0, INT_2);
                localDateTime = LocalDate.of(parseInt(year), parseInt(dateStr.substring(INT_2, INT_4)), parseInt(dateStr.substring(INT_4, INT_6))).atStartOfDay();
            } else {
                return logInvalidDate(dateStr);
            }
        } else if (dateStr.length() == INT_8) {
            if (dateStr.contains(HMS_SYMBOL)) {
                String[] arrays = dateStr.split(HMS_SYMBOL);
                if (arrays.length != INT_3) {
                    return logInvalidDate(dateStr);
                }
                localDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.of(parseInt(arrays[INT_0]), parseInt(arrays[INT_1]), parseInt(arrays[INT_2])));
            } else if (RegexKit.contains(dateStr, YMD_SYMBOL)) {
                String[] arrays = dateStr.split(YMD_SYMBOL);
                if (arrays.length != INT_3) {
                    return logInvalidDate(dateStr);
                }
                localDateTime = LocalDate.of(parseInt(arrays[INT_0]), parseInt(arrays[INT_1]), parseInt(arrays[INT_2])).atStartOfDay();
            } else {
                return parse(dateStr, DATE_FORMAT_WITHOUT_SYMBOL);
            }
        } else if (dateStr.length() == INT_10) {
            return parse(formatDateStr(dateStr), DATE_FORMAT);
        } else if (dateStr.length() == INT_19) {
            return parse(formatDateStr(dateStr), DATE_TIME_FORMAT);
        }
        return null == localDateTime ? logInvalidDate(dateStr) : fromLocalDate(localDateTime);
    }

    private static String formatDateStr(String dateStr) {
        return dateStr.replace(SymbolEnum.dot.string, SymbolEnum.minus.string).replace(SymbolEnum.sprit.string, SymbolEnum.minus.string);
    }

    public static Date toDate(String dateStr) {
        return com.jfinal.ext.kit.DateKit.toDate(dateStr);
    }

    public static String toStr(Date date) {
        return com.jfinal.ext.kit.DateKit.toStr(date);
    }

    public static String toStr(Date date, String pattern) {
        return com.jfinal.ext.kit.DateKit.toStr(date, pattern);
    }

    public static Date fromLocalDate(LocalDate localDate) {
        if (null == localDate) {
            return null;
        }
        return fromLocalDate(localDate.atStartOfDay());
    }

    public static Date fromLocalDate(LocalDateTime localDateTime) {
        if (null == localDateTime) {
            return null;
        }
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);
        Instant instant = zonedDateTime.toInstant();
        return Date.from(instant);
    }

    /**
     * 数据库类型 是 date 时，Instant instant = date.toInstant(); 会异常
     * 所以要使用 datetime
     */
    public static LocalDateTime toLocalDateTime(Date date) {
        Instant instant = date.toInstant();
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime zonedDateTime = instant.atZone(zoneId);
        return zonedDateTime.toLocalDateTime();
    }

    public static LocalDate toLocalDate(Date date) {
        return toLocalDateTime(date).toLocalDate();
    }

    public static Date parse(String str) {
        return toDate(str);
    }

    public static Date parse(String dateStr, String pattern) {
        if (StringKit.isBlank(dateStr) || StringKit.isBlank(pattern)) {
            return null;
        }
        SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
        try {
            return dateFormat.parse(dateStr);
        } catch (ParseException e) {
            throw new IllegalArgumentException("The date format is not supported for the time being! pattern: " + pattern + ", date: " + dateStr);
        }
    }

    public static String format(Date date) {
        return toStr(date);
    }

    public static String format(Date date, String pattern) {
        return toStr(date, pattern);
    }

    public static LocalDateTime toLocalDateTime(String dateStr) {
        int length10 = 10;
        int length19 = 19;
        int length24 = 24;
        int length = dateStr.length();
        LocalDate localDate = null;
        LocalTime localTime = null;
        if (length >= length10) {
            localDate = LocalDate.parse(dateStr.substring(0, 10));
            if (length == length19) {
                localTime = LocalTime.parse(dateStr.substring(11) + ".000");
            } else if (length > length19 && length < length24 && dateStr.contains(SymbolEnum.dot.string)) {
                localTime = LocalTime.parse(dateStr.substring(11));
            }
        }
        if (null == localDate) {
            return null;
        }
        if (null == localTime) {
            return localDate.atStartOfDay();
        }
        return LocalDateTime.of(localDate, localTime);
    }

    public static boolean isBefore(Date before, int days) {
        long beforeMillis = before.getTime();
        long nowMillis = System.currentTimeMillis();

        double beforeDays = ((double) (nowMillis - beforeMillis)) / (3600 * 24 * 1000);
        return beforeDays > days;
    }

    public static boolean isAfter(Date after, int days) {
        long afterMillis = after.getTime();
        long nowMillis = System.currentTimeMillis();

        double afterDays = ((double) (afterMillis - nowMillis)) / (3600 * 24 * 1000);
        return afterDays > days;
    }

    public static String formatNowToZeroZone() {
        return formatToZeroZone(System.currentTimeMillis());
    }

    public static String formatToZeroZone(Date date) {
        return formatToZeroZone(date.getTime());
    }

    public static String formatToZeroZone(Long targetTimeMillis) {
        return formatToZeroZone(targetTimeMillis, null);
    }

    public static String formatNowToZeroZone(String regex) {
        return formatToZeroZone(System.currentTimeMillis(), regex);
    }

    public static String formatToZeroZone(Date date, String regex) {
        return formatToZeroZone(date.getTime(), regex);
    }

    public static String formatToZeroZone(Long targetTimeMillis, String regex) {
        if (null == targetTimeMillis) {
            return null;
        }
        long newTimeMillis = targetTimeMillis - 8 * 60 * 60 * 1000;

        LocalDateTime target = DateKit.toLocalDateTime(new Date(newTimeMillis));
        return String.format("%d%02d%02dT%02d%02d%02dZ", target.getYear(), target.getMonthValue(), target.getDayOfMonth(), target.getHour(), target.getMinute(), target.getSecond());
//        return format(new Date(newTimeMillis), StringKit.isBlank(regex) ? DEFAULT_ZERO_ZONE_REGEX : regex);
    }

    public static Integer getSeason() {
        return getSeason(LocalDate.now());
    }

    public static Integer getSeason(Date date) {
        if (null == date) {
            return null;
        }
        return getSeason(toLocalDate(date));
    }

    public static Integer getSeason(LocalDateTime localDateTime) {
        if (null == localDateTime) {
            return null;
        }
        return (localDateTime.getMonthValue() - Numbers.INT_1) / Numbers.INT_3 + Numbers.INT_1;
    }

    public static Integer getSeason(LocalDate localDate) {
        if (null == localDate) {
            return null;
        }
        return (localDate.getMonthValue() - Numbers.INT_1) / Numbers.INT_3 + Numbers.INT_1;
    }

    public static LocalDate getSeasonFirstLocalDate(Integer seasonValue) {
        if (Numbers.INT_1 > seasonValue || Numbers.INT_4 < seasonValue) {
            return null;
        }
        LocalDate now = LocalDate.now();
        int firstMonth = (seasonValue - Numbers.INT_1) * Numbers.INT_3 + Numbers.INT_1;
        return LocalDate.of(now.getYear(), firstMonth, Numbers.INT_1);
    }

    public static LocalDate getSeasonLastLocalDate(Integer seasonValue) {
        if (Numbers.INT_1 > seasonValue || Numbers.INT_4 < seasonValue) {
            return null;
        }
        LocalDate now = LocalDate.now();
        int firstMonth = (seasonValue - Numbers.INT_1) * Numbers.INT_3 + Numbers.INT_3;
        LocalDate lastMonthFirstDay = LocalDate.of(now.getYear(), firstMonth, Numbers.INT_1);
        return LocalDate.of(lastMonthFirstDay.getYear(), lastMonthFirstDay.getMonthValue(), lastMonthFirstDay.lengthOfMonth());
    }

    public static LocalDateTime getSeasonFirstLocalDateTime(Integer seasonValue) {
        LocalDate firstLocalDate = getSeasonFirstLocalDate(seasonValue);
        if (null == firstLocalDate) {
            return null;
        }
        return firstLocalDate.atStartOfDay();
    }

    public static LocalDateTime getSeasonLastLocalDateTime(Integer seasonValue) {
        LocalDate lastLocalDate = getSeasonLastLocalDate(seasonValue);
        if (null == lastLocalDate) {
            return null;
        }
        return lastLocalDate.atTime(LocalTime.MAX);
    }

    public static Date getSeasonFirstDate(Integer seasonValue) {
        LocalDateTime firstLocalDateTime = getSeasonFirstLocalDateTime(seasonValue);
        if (null == firstLocalDateTime) {
            return null;
        }
        return DateKit.fromLocalDate(firstLocalDateTime);
    }

    public static Date getSeasonLastDate(Integer seasonValue) {
        LocalDateTime lastLocalDateTime = getSeasonLastLocalDateTime(seasonValue);
        if (null == lastLocalDateTime) {
            return null;
        }
        return DateKit.fromLocalDate(lastLocalDateTime);
    }

    public static LocalDateTime getSeasonLastLocalDateTime() {
        int seasonValue = getSeason();
        return getSeasonLastLocalDateTime(seasonValue);
    }

    public static LocalDateTime getSeasonFirstLocalDateTime() {
        int seasonValue = getSeason();
        return getSeasonLastLocalDateTime(seasonValue);
    }

    public static LocalDate getSeasonFirstLocalDate() {
        int seasonValue = getSeason();
        return getSeasonFirstLocalDate(seasonValue);
    }

    public static LocalDate getSeasonLastLocalDate() {
        int seasonValue = getSeason();
        return getSeasonFirstLocalDate(seasonValue);
    }

    public static Date getSeasonFirstDate() {
        int seasonValue = getSeason();
        return getSeasonFirstDate(seasonValue);
    }

    public static Date getSeasonLastDate() {
        int seasonValue = getSeason();
        return getSeasonLastDate(seasonValue);
    }
}
